package com.example.mytestdemo.linearlayoutmanager

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.widget.Scroller
import androidx.annotation.FloatRange


/**
 * @author tgw
 * @date 2022/8/25
 * @describe 拖动旋转的
 */
class ArcSlidingHelper {

    companion object {
        private const val TAG = "ArcSlidingHelper"
    }

    lateinit var mContext: Context
    var mPivotX: Int = 0
    var mPivotY: Int = 0
    lateinit var mScroller: Scroller
    lateinit var mVelocityTracker: VelocityTracker
    lateinit var mListener: OnSlidingListener

    constructor()


    private constructor(
        context: Context,
        pivotX: Int,
        pivotY: Int,
        listener: OnSlidingListener
    ) {
        mPivotX = pivotX
        mPivotY = pivotY
        mListener = listener
        mScroller = Scroller(context)
        mVelocityTracker = VelocityTracker.obtain()
    }


     var mHandler: InertialSlidingHandler?=null

    /**
     * 创建ArcSlidingHelper对象
     *
     * @param targetView 接受滑动手势的View (圆弧滑动事件以此View的中心点为圆心)
     * @param listener   当发生圆弧滚动时的回调
     * @return ArcSlidingHelper
     */
    fun create(targetView: View, listener: OnSlidingListener): ArcSlidingHelper {
        var width: Int = targetView.getWidth()
        var height: Int = targetView.getHeight()
        //如果宽度为0，提示宽度无效，需要调用updatePivotX方法来设置x轴的旋转基点
        if (width == 0) {
            Log.e(
                TAG,
                "targetView width = 0! please invoke the updatePivotX(int) method to update the PivotX!",
                RuntimeException()
            )
        }
        //如果高度为0，提示高度无效，需要调用updatePivotY方法来设置y轴的旋转基点
        if (height == 0) {
            Log.e(
                TAG,
                "targetView height = 0! please invoke the updatePivotY(int) method to update the PivotY!",
                RuntimeException()
            )
        }
//        想问一下，获取View在屏幕的位置为啥不直接使用：
        val xylocation = IntArray(2)
        var location = targetView.getLocationOnScreen(xylocation)
        Log.d(TAG, "create: location:--${location.toString()}")
        width /= 2
        height /= 2
//        val x = getAbsoluteX(targetView).toInt()
//        val y = getAbsoluteY(targetView).toInt()

        val x = xylocation[0]
        val y = xylocation[1]
        val arcSlidingHelper =
            ArcSlidingHelper(targetView.getContext(), x + width, y + height, listener)
        //惯性滑动的事件处理
        arcSlidingHelper.mHandler = InertialSlidingHandler(arcSlidingHelper, Looper.getMainLooper())
        return arcSlidingHelper
    }

    /**
     * 获取view在屏幕中的绝对x坐标
     */
    private fun getAbsoluteX(view: View): Float {
        var x = view.x
        val parent = view.parent
        if (parent != null && parent is View) {
            x += getAbsoluteX(parent as View)
        }
        return x
    }

    /**
     *是否开启惯性滑动
     */
    var isInertialSlidingEnable: Boolean = false

    /**
     * 获取view在屏幕中的绝对y坐标
     */
    private fun getAbsoluteY(view: View): Float {
        var y = view.y
        val parent = view.parent
        if (parent != null && parent is View) {
            y += getAbsoluteY(parent as View)
        }
        return y
    }

    var mStartX: Float = 0f
    var mStartY: Float = 0f

    var mEndX: Float = 0f
    var mEndY: Float = 0f

    fun handleMovement(event: MotionEvent) {
        //checkIsRecycled就是检测是否已经调用过release方法(释放资源)，如果资源已回收则拋异常。
//        checkIsRecycled()
        val x: Float
        val y: Float
        //我们还判断了isSelfSliding，这个表示接受触摸事件的和实际旋转的都是同一个View。
        if (false) {
            x = event.rawX
            y = event.rawY
        } else {
            x = event.x
            y = event.y
        }
        mVelocityTracker.addMovement(event)
        when (event.action) {
            MotionEvent.ACTION_DOWN ->{
                if (!mScroller.isFinished) {
                    mScroller.abortAnimation()
                }

            }
            MotionEvent.ACTION_MOVE -> handleActionMove(x, y)
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_OUTSIDE -> if (isInertialSlidingEnable) {//是否惯性滑动
                mVelocityTracker.computeCurrentVelocity(1000)
                mScroller.fling(
                    0,
                    0,
                    mVelocityTracker.xVelocity.toInt(),
                    mVelocityTracker.yVelocity.toInt(),
                    Int.MIN_VALUE,
                    Int.MAX_VALUE,
                    Int.MIN_VALUE,
                    Int.MAX_VALUE
                )
//                mEndX = x
//                mEndY = y
                startFling()
            }
            else -> {}
        }
        mStartX = x
        mStartY = y
    }


    /**
     *们用computeInertialSliding来代替了View中的computeScroll方法，用startFling代替了invalidate，
     * 可以说是完全脱离了View来使用Scroller，妙就妙在这里啦，嘻嘻。
     */
    private fun startFling() {
        this.mHandler?.sendEmptyMessage(0)
    }

    //是否是顺时针滑动
    var isClockwiseScrolling = false

    private fun handleActionMove(x: Float, y: Float) {
        //              __________
        //根据公式 bc = √ ab² + ac² 计算出对角线的长度

        //圆心到起始点的线条长度
        val lineA =
            Math.sqrt(
                Math.pow(
                    Math.abs(mStartX - mPivotX).toDouble(),
                    2.0
                ) + Math.pow(Math.abs(mStartY - mPivotY).toDouble(), 2.0)
            )
                .toFloat()
        //圆心到结束点的线条长度
        val lineB =
            Math.sqrt(
                Math.pow(
                    Math.abs(x - mPivotX).toDouble(),
                    2.0
                ) + Math.pow(Math.abs(y - mPivotY).toDouble(), 2.0)
            )
                .toFloat()
        //起始点到结束点的线条长度
        val lineC =
            Math.sqrt(
                Math.pow(Math.abs(x - mStartX).toDouble(), 2.0) + Math.pow(
                    Math.abs(y - mStartY)
                        .toDouble(), 2.0
                )
            )
                .toFloat()
        if (lineC > 0 && lineA > 0 && lineB > 0) {
            //根据公式 cosC = (a² + b² - c²) / 2ab
            val angle: Float = fixAngle(
                //获取角度
                Math.toDegrees(
                    //获取弧度
                    Math.acos(
                        (Math.pow(
                            lineA.toDouble(),
                            2.0
                        ) + Math.pow(lineB.toDouble(), 2.0) - Math.pow(
                            lineC.toDouble(),
                            2.0
                        )) / (2 * lineA * lineB)
                    )
                )
                    .toFloat()
            )
            if (!java.lang.Float.isNaN(angle)) {
                mListener.onSliding(if (isClockwise(x, y).also {
                        isClockwiseScrolling = it
                    }) angle else -angle)
            }
        }
    }


    private fun isClockwise(mEndX: Float, mEndY: Float): Boolean {
        val isClockwise: Boolean
        //垂直滑动  上下滑动的幅度 > 左右滑动的幅度，则认为是垂直滑动，反之
        val isVerticalScroll: Boolean = Math.abs(mEndY - mStartY) > Math.abs(mEndX - mStartX)
        //惯性滑动的速率变量
        isShouldBeGetY = isVerticalScroll
        //手势向下
        val isGestureDownward: Boolean = mEndY > mStartY
        //手势向右
        val isGestureRightward: Boolean = mEndX > mStartX
        isClockwise = if (isVerticalScroll) {
            //如果手指滑动的地方是在圆心左边的话：向下滑动就是逆时针，向上滑动则顺时针。反之，如果在圆心右边，向下滑动是顺时针，向上则逆时针。
            mEndX < mPivotX != isGestureDownward
        } else {
            //逻辑同上：手指滑动在圆心的上方：向右滑动就是顺时针，向左就是逆时针。反之，如果在圆心的下方，向左滑动是顺时针，向右是逆时针。
            mEndY < mPivotY == isGestureRightward
        }
        return isClockwise
    }


    /**
     * 调整角度，使其在0 ~ 360之间
     *
     * @param rotation 当前角度
     * @return 调整后的角度
     */
    private fun fixAngle(rotation: Float): Float {
        var rotation = rotation
        val angle = 360f
        if (rotation < 0) {
            rotation += angle
        }
        if (rotation > angle) {
            rotation %= angle
        }
        return rotation
    }

    var mLastScrollOffset: Float = 0f

    /**
     * 处理惯性滚动
     */
    ///我们在调用computeScrollOffset方法(更新currX和currY的值)之后，
    // 判断isShouldBeGetY来决定究竟是getCurrX好还是getCurrY好，
    // 这个isShouldBeGetY的值就是在判断是否顺时针旋转的时候更新的，
    // 我们不是有一个 isVerticalScroll (是否垂直滑动)吗，isShouldBeGetY的值其实也就是isVerticalScroll的值
    // ，因为如果是垂直滑动的话，VelocityTracker的Y速率会更大，所以这个时候getCurrY是很明智的，
    // 反之。 在确定好了get哪个值之后，我们还将它跟mScrollAvailabilityRatio相乘，
    // 这个mScrollAvailabilityRatio就是速率的利用率，默认是0.3，就是用来缩短惯性滚动的距离的，
    // 因为在测试的时候，觉得这个惯性滚动的距离有点长，轻轻一划就转了十几圈，好像很轻的样子
    var isShouldBeGetY: Boolean = false
    fun computeInertialSliding() {
//        checkIsRecycled()
        if (mScroller.computeScrollOffset()) {
            val y: Float =
                (if (isShouldBeGetY) mScroller.currY else mScroller.currX) * mScrollAvailabilityRatio
            if (mLastScrollOffset != 0f) {
                val offset = fixAngle(Math.abs(y - mLastScrollOffset))
                mListener.onSliding(if (isClockwiseScrolling) offset else -offset)
            }
            mLastScrollOffset = y
            startFling()
        } else if (mScroller.isFinished) {
            mLastScrollOffset = 0f
        }
    }

    var mScrollAvailabilityRatio = 0.1f

    /**
     * VelocityTracker的惯性滚动利用率
     * 数值越大，惯性滚动的动画时间越长
     *
     * @param ratio (范围: 0~1)
     */
    fun setScrollAvailabilityRatio(@FloatRange(from = 0.0, to = 1.0) ratio: Float) {
        mScrollAvailabilityRatio = ratio
    }

}

/**
 *
 *惯性滑动的事件处理
 */
class InertialSlidingHandler internal constructor(var mHelper: ArcSlidingHelper,looper: Looper) :
    Handler(looper) {
    override fun handleMessage(msg: Message) {
        mHelper.computeInertialSliding()
    }
}


interface OnSlidingListener {
    fun onSliding(offset: Float)
}